//! 泛微OA认证过程的加密模块
//!
//! 泛微OA的认证过程中，使用到非对称加密（RSA PKCS#8）。通过注册得到public key，然后格式化，然后加密secret，
//! 返回加密后的base64字符串。
//! 如果加密失败，则返回空字符串。

use base64::{engine::general_purpose, Engine as _};
use rsa::{pkcs8::DecodePublicKey, Error, Pkcs1v15Encrypt, RsaPublicKey};

/// 解析pem格式的rsa public key;
/// rust 中的pem换行后前边的空格一定要删除，否则解析错误。
/// 错误结果还未想好如何处理。
/// 返回值是一个Result类型，抛出错误，后续解决。
///
/// # Example
///
/// ```rust
/// use weaver_oa::crypto;
///
/// let pem = "-----BEGIN PUBLIC KEY-----
/// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hjimi+sVowj1Cas3BBR
/// InR552IBRl95wu64UIM5nxg2IhnZOkQBJ8TMdH8wFh0Jvb50isb1dqiILRQu2wWO
/// 1nbJblwVY8LMcQJF9txVNith21WF7fYDTk1TVJLXwyYJj286zxTDqUdL2cgou+cV
/// VukCLrxvk5MKC4NhmtIgK+zIhKm1yL5HV0pLLXT33W1HJpleYXuDeQJybCQ4MWzP
/// DaZtTdc4/ssh5uv3cFXZjpDSQu0mzxbp6oor3qesgyfdgxkh1l/RxiGSzo4MEnht
/// 4VhqKsr/x4ER6ShXnHca9F7mUFPOavQ05fwdZaIUdM5dnqlF51YRwdfgwRuUDD22
/// awIDAQAB
/// -----END PUBLIC KEY-----";
///
/// let result = crypto::parse_public_key(pem);
///
/// assert!(result.is_ok());
/// ```
pub fn parse_public_key(pem_string: &str) -> Result<RsaPublicKey, rsa::pkcs8::spki::Error> {
    return RsaPublicKey::from_public_key_pem(pem_string);
}

/// 泛微OA的通过register接口获取到的spk不是标准的，需要进一步格式化。该函数实现了
/// 将OA的spk转成标准的pem格式，用于后续的读取和加密。字符串宽度设为64。
/// 返回值是格式化后的字符串。
///
/// # Example
///
/// ```rust
/// use weaver_oa::crypto;
///
/// let a = String::from("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTCs5SpyH3/I2tA2KVCQn68i99bhVGUz6Jkaa3GUe0aB1Sui8RX1Ns6nSl/SIcbvtm3Na4hxt5dU/i3vXMlaQAmbetmV5ivu5Zna40aDezw38icBOk0YWlUhMEsHq+IlUUpJbmtK9x7PnAnCy3rHietgwanTm/leju14TgW6IeLtw/m9GcX7FbuzoogRzk1uR6J3/1qzj5+4KykUYDTeDoa7xz65d12doOOI2mkrixa9KxXJznbXgbd+xtnyTrsUTydrpnHAOH+SIaOrrEhItCtUQzAvlPI/668ttmu/k5mnFfgxNj38MJc4sGlS0DPR4cXPD/uLqgh/jgPVpEtwKwIDAQAB");
///
/// let p = crypto::format_weaver_public_key(a);
/// let r = crypto::parse_public_key(p.as_str());
///
/// assert!(r.is_ok());
/// ```
pub fn format_weaver_public_key(pem_string: String) -> String {
    let mut result = String::new();
    result.push_str("-----BEGIN PUBLIC KEY-----\n");

    for (i, v) in pem_string.chars().enumerate() {
        if i != 0 && i % 64 == 0 {
            result.push_str("\n");
        }
        result.push(v);
    }

    result.push_str("\n-----END PUBLIC KEY-----\n");
    return result;
}

/// 通过公钥加密给定的字符串。封装一步，用于后续处理。
/// 返回值是Result类型
/// 加密后的vec数组。
///
/// # Example
///
/// ```
/// use weaver_oa::crypto;
///
/// let pem = "-----BEGIN PUBLIC KEY-----
/// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hjimi+sVowj1Cas3BBR
/// InR552IBRl95wu64UIM5nxg2IhnZOkQBJ8TMdH8wFh0Jvb50isb1dqiILRQu2wWO
/// 1nbJblwVY8LMcQJF9txVNith21WF7fYDTk1TVJLXwyYJj286zxTDqUdL2cgou+cV
/// VukCLrxvk5MKC4NhmtIgK+zIhKm1yL5HV0pLLXT33W1HJpleYXuDeQJybCQ4MWzP
/// DaZtTdc4/ssh5uv3cFXZjpDSQu0mzxbp6oor3qesgyfdgxkh1l/RxiGSzo4MEnht
/// 4VhqKsr/x4ER6ShXnHca9F7mUFPOavQ05fwdZaIUdM5dnqlF51YRwdfgwRuUDD22
/// awIDAQAB
/// -----END PUBLIC KEY-----";
///
/// let result = crypto::parse_public_key(pem);
///
/// assert!(result.is_ok());
/// let r = crypto::encrypt(result.unwrap(), "Wo".to_string());
/// assert!(r.is_ok());
/// ```
pub fn encrypt(pub_key: RsaPublicKey, string: String) -> Result<Vec<u8>, Error> {
    // pub_key.encrypt(rng, padding, msg)

    let mut rng = rand::thread_rng();

    return pub_key.encrypt(&mut rng, Pkcs1v15Encrypt, string.as_bytes());
}

/// 通过泛微OA的register接口拿到的secret和spk，进行加密
/// 返回加密后的字符串。用于获取token。
/// **如果返回是空字符串，则加密失败**
/// 后续添加日志记录。
///
/// # Example
///
/// ```rust
/// use weaver_oa::crypto;
///
/// let spk = String::from("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTCs5SpyH3/I2tA2KVCQn68i99bhVGUz6Jkaa3GUe0aB1Sui8RX1Ns6nSl/SIcbvtm3Na4hxt5dU/i3vXMlaQAmbetmV5ivu5Zna40aDezw38icBOk0YWlUhMEsHq+IlUUpJbmtK9x7PnAnCy3rHietgwanTm/leju14TgW6IeLtw/m9GcX7FbuzoogRzk1uR6J3/1qzj5+4KykUYDTeDoa7xz65d12doOOI2mkrixa9KxXJznbXgbd+xtnyTrsUTydrpnHAOH+SIaOrrEhItCtUQzAvlPI/668ttmu/k5mnFfgxNj38MJc4sGlS0DPR4cXPD/uLqgh/jgPVpEtwKwIDAQAB");
/// let secret = "secret".to_string();
///
/// let p = crypto::oa_encrypt_secret(secret, spk);
///
/// assert_ne!(p.len(), 0);
/// ```
pub fn oa_encrypt_secret(secret: String, spk: String) -> String {
    let mut result = "".to_string();
    let format_key = format_weaver_public_key(spk);
    let public_key = parse_public_key(format_key.as_str());
    if public_key.is_err() {
        return result;
    }

    let enc = encrypt(public_key.unwrap(), secret);
    if enc.is_err() {
        return result;
    }

    result = general_purpose::STANDARD.encode(enc.unwrap());
    return result;
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_parse_public_key() {
        let pem = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hjimi+sVowj1Cas3BBR
InR552IBRl95wu64UIM5nxg2IhnZOkQBJ8TMdH8wFh0Jvb50isb1dqiILRQu2wWO
1nbJblwVY8LMcQJF9txVNith21WF7fYDTk1TVJLXwyYJj286zxTDqUdL2cgou+cV
VukCLrxvk5MKC4NhmtIgK+zIhKm1yL5HV0pLLXT33W1HJpleYXuDeQJybCQ4MWzP
DaZtTdc4/ssh5uv3cFXZjpDSQu0mzxbp6oor3qesgyfdgxkh1l/RxiGSzo4MEnht
4VhqKsr/x4ER6ShXnHca9F7mUFPOavQ05fwdZaIUdM5dnqlF51YRwdfgwRuUDD22
awIDAQAB
-----END PUBLIC KEY-----";

        let r = parse_public_key(pem);
        assert!(r.is_ok());
    }

    #[test]
    fn test_format_weaver_public_key() {
        let a = String::from("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTCs5SpyH3/I2tA2KVCQn68i99bhVGUz6Jkaa3GUe0aB1Sui8RX1Ns6nSl/SIcbvtm3Na4hxt5dU/i3vXMlaQAmbetmV5ivu5Zna40aDezw38icBOk0YWlUhMEsHq+IlUUpJbmtK9x7PnAnCy3rHietgwanTm/leju14TgW6IeLtw/m9GcX7FbuzoogRzk1uR6J3/1qzj5+4KykUYDTeDoa7xz65d12doOOI2mkrixa9KxXJznbXgbd+xtnyTrsUTydrpnHAOH+SIaOrrEhItCtUQzAvlPI/668ttmu/k5mnFfgxNj38MJc4sGlS0DPR4cXPD/uLqgh/jgPVpEtwKwIDAQAB");
        let p = format_weaver_public_key(a);
        let r = parse_public_key(p.as_str());
        assert!(r.is_ok());
    }

    #[test]
    fn test_encrypt() {
        let pem = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Hjimi+sVowj1Cas3BBR
InR552IBRl95wu64UIM5nxg2IhnZOkQBJ8TMdH8wFh0Jvb50isb1dqiILRQu2wWO
1nbJblwVY8LMcQJF9txVNith21WF7fYDTk1TVJLXwyYJj286zxTDqUdL2cgou+cV
VukCLrxvk5MKC4NhmtIgK+zIhKm1yL5HV0pLLXT33W1HJpleYXuDeQJybCQ4MWzP
DaZtTdc4/ssh5uv3cFXZjpDSQu0mzxbp6oor3qesgyfdgxkh1l/RxiGSzo4MEnht
4VhqKsr/x4ER6ShXnHca9F7mUFPOavQ05fwdZaIUdM5dnqlF51YRwdfgwRuUDD22
awIDAQAB
-----END PUBLIC KEY-----";

        let result = parse_public_key(pem);

        assert!(result.is_ok());
        let r = encrypt(result.unwrap(), "Wo".to_string());
        assert!(r.is_ok());
    }

    #[test]
    fn test_oa_encrypt_secret() {
        let spk = String::from("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTCs5SpyH3/I2tA2KVCQn68i99bhVGUz6Jkaa3GUe0aB1Sui8RX1Ns6nSl/SIcbvtm3Na4hxt5dU/i3vXMlaQAmbetmV5ivu5Zna40aDezw38icBOk0YWlUhMEsHq+IlUUpJbmtK9x7PnAnCy3rHietgwanTm/leju14TgW6IeLtw/m9GcX7FbuzoogRzk1uR6J3/1qzj5+4KykUYDTeDoa7xz65d12doOOI2mkrixa9KxXJznbXgbd+xtnyTrsUTydrpnHAOH+SIaOrrEhItCtUQzAvlPI/668ttmu/k5mnFfgxNj38MJc4sGlS0DPR4cXPD/uLqgh/jgPVpEtwKwIDAQAB");
        let secret = "secret".to_string();

        let p = oa_encrypt_secret(secret, spk);
        assert_ne!(p.len(), 0);
    }
}
